/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input;


import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.lex.input.action.MouseScrollAction;
import org.lex.input.action.MouseSphericalLook;
import org.lex.input.action.MouseZoom;
import org.lex.input.effects.HeightMap;
import org.lex.input.effects.MinMaxCurrent;
import org.lex.input.effects.filter.AccelFilter;
import org.lex.input.effects.filter.AccelFilter2d;
import org.lex.input.effects.filter.LinearFilter2d;
import org.lex.input.effects.filter.SpringFilter;
import org.lex.input.effects.interpolation.SpringInterpolation;
import org.lex.input.mouse.Cursor;
import org.lex.input.mouse.MouseBindingManager;
import org.lex.input.mouse.MouseManager;

import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.action.InputActionEvent;
import com.jme.input.controls.binding.MouseButtonBinding;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.Spatial;

/**
 * InputHandler for strategy games.
 * 
 * Remember, you absolutely must call useCursor() every frame
 * for mouse states to update correctly.
 * 
 * You can set camera position and orientation using:
 * camera.setLocation(handler.getCameraLocation());
 * camera.lookAt(handler.getWorldTranslation());
 * 
 * @author lex
 * 
 */
public class StrategicHandler extends InputHandler {
	
	@SuppressWarnings("unused")
	private static final Logger log = Logger.getLogger(
			StrategicHandler.class.getName());
	
	private static final float DEFAULT_MASS = 1;
	
	public static final float DEFAULT_CAM_HEIGHT_OFFSET = 25;
	public static final boolean DEFAULT_KEYBOARD_LOOK_ENABLED = true;
	public static final boolean DEFAULT_KEYBOARD_SCROLL_ENABLED = true;
	public static final boolean DEFAULT_MOUSE_LOOK_ENABLED = true;
	public static final boolean DEFAULT_MOUSE_SCROLL_ENABLED = true;
	
	public static final boolean DEFAULT_INVERT_MOUSE_X = false;
	public static final boolean DEFAULT_INVERT_MOUSE_Y = false;
	public static final boolean DEFAULT_INVERT_MOUSE_WEEL = false;
	public static final float DEFAULT_SCROLL_BORDER = 0.5f;
	
	public static final float DEFAULT_FOLLOW_SPRING = 40;
	public static final float DEFAULT_FOLLOW_DAMPING = 10;
	
	public static final float DEFAULT_MOUSE_ROTATION_SENSITIVITY = 0.3f;
	public static final float DEFAULT_MOUSE_ROTATION_INTERPOLATION = 0.040f;
	public static final float DEFAULT_MOUSE_ZOOM_SENSITIVITY = 0.15f;
	public static final float DEFAULT_MOUSE_ZOOM_SPRING = 40;
	public static final float DEFAULT_MOUSE_ZOOM_DAMPING = 10;
	
	public static final float DEFAULT_KEYBOARD_HORIZONTAL_FORCE = 300;
	public static final float DEFAULT_KEYBOARD_HORIZONTAL_DAMPING = 10;
	public static final float DEFAULT_KEYBOARD_HORIZONTAL_MAXSPEED = 200;
	public static final float DEFAULT_KEYBOARD_VERTICAL_FORCE = 200;
	public static final float DEFAULT_KEYBOARD_VERTICAL_DAMPING = 10;
	public static final float DEFAULT_KEYBOARD_VERTICAL_MAXSPEED = 75;
	public static final float DEFAULT_KEYBOARD_ZOOM_FORCE = 600;
	public static final float DEFAULT_KEYBOARD_ZOOM_DAMPING = 7;
	public static final float DEFAULT_KEYBOARD_ZOOM_MAXSPEED = 250;
	
	public static final float DEFAULT_MOUSE_SCROLL_FORCE = 800;
	public static final float DEFAULT_MOUSE_SCROLL_DAMPING = 10;
	public static final float DEFAULT_MOUSE_SCROLL_MAXSPEED = 500;
	public static final float DEFAULT_KEYBOARD_SCROLL_FORCE = 800;
	public static final float DEFAULT_KEYBOARD_SCROLL_DAMPING = 10;
	public static final float DEFAULT_KEYBOARD_SCROLL_MAXSPEED = 500;
	
	public static final float DEFAULT_VERTICAL_ROTATION_MIN = 0;
	public static final float DEFAULT_VERTICAL_ROTATION_MAX = 89.9f;
	public static final float DEFAULT_VERTICAL_ROTATION_CURRENT = 45;
	public static final float DEFAULT_HORIZONTAL_ROTATION_MIN = 0;
	public static final float DEFAULT_HORIZONTAL_ROTATION_MAX = 360;
	public static final float DEFAULT_HORIZONTAL_ROTATION_CURRENT = 0;
	public static final float DEFAULT_ZOOM_DISTANCE_MIN = 25;
	public static final float DEFAULT_ZOOM_DISTANCE_MAX = 200;
	public static final float DEFAULT_ZOOM_DISTANCE_CURRENT = 100;
	
	public static final Vector3f DEFAULT_SCROLL_XVEC = Vector3f.UNIT_X;
	public static final Vector3f DEFAULT_SCROLL_YVEC = Vector3f.UNIT_Z.negate();
	public static final Vector3f DEFAULT_WORLD_UP = Vector3f.UNIT_Y;
	public static final Vector3f DEFAULT_DIRECTION = Vector3f.UNIT_Z.negate();
	public static final Vector3f DEFAULT_WORLD_LEFT = Vector3f.UNIT_X.negate();
	
	public static final int DEFAULT_KEY_TOGGLE_MOUSE_LOOK =
		KeyInput.KEY_LCONTROL;
	public static final int DEFAULT_KEY_TOGGLE_HARDWARE_MOUSE =
		KeyInput.KEY_NUMPADENTER;
	public static final int DEFAULT_MB_TOGGLE_MOUSE_LOOK =
		MouseButtonBinding.RIGHT_BUTTON;
	
	public static final int DEFAULT_KEY_ROTATE_UP = KeyInput.KEY_HOME;
	public static final int DEFAULT_KEY_ROTATE_DOWN = KeyInput.KEY_END;
	public static final int DEFAULT_KEY_ROTATE_LEFT = KeyInput.KEY_DELETE;
	public static final int DEFAULT_KEY_ROTATE_RIGHT = KeyInput.KEY_PGDN;
	public static final int DEFAULT_KEY_ZOOM_IN = KeyInput.KEY_PGUP;
	public static final int DEFAULT_KEY_ZOOM_OUT = KeyInput.KEY_INSERT;
	
	public static final int DEFAULT_KEY_SCROLL_UP = KeyInput.KEY_UP;
	public static final int DEFAULT_KEY_SCROLL_DOWN = KeyInput.KEY_DOWN;
	public static final int DEFAULT_KEY_SCROLL_LEFT = KeyInput.KEY_LEFT;
	public static final int DEFAULT_KEY_SCROLL_RIGHT = KeyInput.KEY_RIGHT;
	
	
	// *************************************************************************
	
	protected Camera camera;
	protected MouseManager mouseManager;
	protected MouseBindingManager mouseBinding = MouseBindingManager.get();
	protected KeyBindingManager keyboard;
	
	// actions are called depending on the switch variables
	protected MouseSphericalLook mouseLook;
	protected MouseScrollAction mouseScroll;
	protected MouseZoom mouseZoom;
	
	private boolean keyboardScrollEnabled;
	private boolean keyboardLookEnabled;
	private boolean mouseScrollEnabled;
	private boolean mouseLookEnabled;
	
	private boolean horizontalRotationEnabled = true;
	private boolean verticalRotationEnabled = true;
	private boolean zoomEnabled = true;

	protected Vector3f cameraLocationNoHeight = new Vector3f();
	/** world translation with height offset */
	private Vector3f finalWorldTranslation = new Vector3f();
	/** camera location with height offset */
	private Vector3f finalCameraLocation = new Vector3f();
	private float camHeightOffset;

	private RotationSphere rotationSphere;
	private ScrollingPlane scrollingPlane;
	
	// filters
	private SpringFilter mouseZoomFilter;
	private LinearFilter2d mouseLookFilter;
	
	private AccelFilter2d mouseScrollFilter;
	private AccelFilter2d keyboardScrollFilter;
	
	private AccelFilter keyboardZoomFilter;
	private AccelFilter keyboardHFilter;
	private AccelFilter keyboardVFilter;
	
	// state
	protected boolean mouseScrolling;
	protected boolean mouseRotating;
	
	// follow object
	private Spatial followObject;
	protected boolean followingObjectRotation;
	protected SpringInterpolation followRotation;
	protected float followAngleOffset;
	
	protected HeightMap heightMap;
	protected CameraEffects cameraEffects;
	
	protected InputActionEvent evt = new InputActionEvent();
	
	/**
	 * Remember, you absolutely must call useCursor() every frame
	 * for mouse states to update correctly.
	 * 
	 * You can set camera position and orientation using:
	 * camera.setLocation(handler.getCameraLocation());
	 * camera.lookAt(handler.getWorldTranslation());
	 * 
	 * @param renderer
	 * @param mouseManager
	 * @param heightMap can be null
	 * @param cameraEffects can be null
	 */
	public StrategicHandler(Camera camera, MouseManager mouseManager,
			HeightMap heightMap)
	{
		keyboard = KeyBindingManager.getKeyBindingManager();
		camHeightOffset = DEFAULT_CAM_HEIGHT_OFFSET;
		
		
		keyboardLookEnabled = DEFAULT_KEYBOARD_LOOK_ENABLED;
		keyboardScrollEnabled = DEFAULT_KEYBOARD_SCROLL_ENABLED;
		mouseLookEnabled = DEFAULT_MOUSE_LOOK_ENABLED;
		mouseScrollEnabled = DEFAULT_MOUSE_SCROLL_ENABLED;
		
		this.camera = camera;
		this.mouseManager = mouseManager;
		this.heightMap = heightMap;
		
		rotationSphere = new RotationSphere(
				DEFAULT_ZOOM_DISTANCE_CURRENT,
				DEFAULT_VERTICAL_ROTATION_CURRENT,
				DEFAULT_HORIZONTAL_ROTATION_CURRENT,
				DEFAULT_DIRECTION,
				DEFAULT_WORLD_UP,
				DEFAULT_WORLD_LEFT);
		rotationSphere.getVertical().setMin(DEFAULT_VERTICAL_ROTATION_MIN);
		rotationSphere.getVertical().setMax(DEFAULT_VERTICAL_ROTATION_MAX);
		rotationSphere.getHorizontal().setMin(DEFAULT_HORIZONTAL_ROTATION_MIN);
		rotationSphere.getHorizontal().setMax(DEFAULT_HORIZONTAL_ROTATION_MAX);
		rotationSphere.getHorizontal().setLoop(true);
		rotationSphere.getZoom().setMin(DEFAULT_ZOOM_DISTANCE_MIN);
		rotationSphere.getZoom().setMax(DEFAULT_ZOOM_DISTANCE_MAX);
		
		scrollingPlane = new ScrollingPlane(camera,
				DEFAULT_SCROLL_XVEC,
				DEFAULT_SCROLL_YVEC,
				DEFAULT_WORLD_UP);
		
		// filters
		mouseLookFilter = new LinearFilter2d(
				DEFAULT_MOUSE_ROTATION_INTERPOLATION);
		
		mouseZoomFilter = new SpringFilter(DEFAULT_MASS, 
				DEFAULT_MOUSE_ZOOM_SPRING,
				DEFAULT_MOUSE_ZOOM_DAMPING);
		
		keyboardHFilter = new AccelFilter(DEFAULT_MASS,
				DEFAULT_KEYBOARD_HORIZONTAL_FORCE,
				DEFAULT_KEYBOARD_HORIZONTAL_DAMPING,
				DEFAULT_KEYBOARD_HORIZONTAL_MAXSPEED);
		
		keyboardVFilter = new AccelFilter(DEFAULT_MASS,
				DEFAULT_KEYBOARD_VERTICAL_FORCE,
				DEFAULT_KEYBOARD_VERTICAL_DAMPING,
				DEFAULT_KEYBOARD_VERTICAL_MAXSPEED);
		
		keyboardZoomFilter = new AccelFilter(DEFAULT_MASS,
				DEFAULT_KEYBOARD_ZOOM_FORCE,
				DEFAULT_KEYBOARD_ZOOM_DAMPING,
				DEFAULT_KEYBOARD_ZOOM_MAXSPEED);
		
		mouseScrollFilter = new AccelFilter2d(DEFAULT_MASS,
				DEFAULT_MOUSE_SCROLL_FORCE,
				DEFAULT_MOUSE_SCROLL_DAMPING,
				DEFAULT_MOUSE_SCROLL_MAXSPEED);
		
		keyboardScrollFilter = new AccelFilter2d(DEFAULT_MASS,
				DEFAULT_KEYBOARD_SCROLL_FORCE,
				DEFAULT_KEYBOARD_SCROLL_DAMPING,
				DEFAULT_KEYBOARD_SCROLL_MAXSPEED);
		
		followRotation = new SpringInterpolation(DEFAULT_MASS,
				DEFAULT_FOLLOW_SPRING,
				DEFAULT_FOLLOW_DAMPING,
				0, 360);
		
		mouseLook = new MouseSphericalLook(mouseManager, mouseLookFilter);
		mouseLook.setXInverted(DEFAULT_INVERT_MOUSE_X);
		mouseLook.setYInverted(DEFAULT_INVERT_MOUSE_Y);
		mouseLook.setSensitivity(DEFAULT_MOUSE_ROTATION_SENSITIVITY);
		
		mouseScroll = new MouseScrollAction(mouseManager, mouseScrollFilter);
		mouseScroll.setBorder(DEFAULT_SCROLL_BORDER);
		mouseScroll.setScrollUpCursor(CURSOR_SCROLL_UP);
		mouseScroll.setScrollLeftCursor(CURSOR_SCROLL_LEFT);
		mouseScroll.setScrollDownCursor(CURSOR_SCROLL_DOWN);
		mouseScroll.setScrollRightCursor(CURSOR_SCROLL_RIGHT);
		mouseScroll.setScrollUpLeftCursor(CURSOR_SCROLL_UPLEFT);
		mouseScroll.setScrollDownLeftCursor(CURSOR_SCROLL_DOWNLEFT);
		mouseScroll.setScrollDownRightCursor(CURSOR_SCROLL_DOWNRIGHT);
		mouseScroll.setScrollUpRightCursor(CURSOR_SCROLL_UPRIGHT);
		
		mouseZoom = new MouseZoom(mouseManager, mouseZoomFilter);
		mouseZoom.setZInverted(DEFAULT_INVERT_MOUSE_WEEL);
		mouseZoom.setSensitivity(DEFAULT_MOUSE_ZOOM_SENSITIVITY);
		
		updateBindings(null);
	}
	
	/**
	 * Call this after changing display settings.
	 */
	public void reinit(int width, int height) {
		mouseManager.reinit(width, height);
		mouseScroll.reinit(width, height);
	}
	
	@Override
	public void update(float time) {
		if (!isEnabled()) return;
		
		super.update(time);
		handleMouse(time);
		handleKeyboard(time);

		// scroll
		if (followObject == null) {
			mouseScrollFilter.update(time);
			keyboardScrollFilter.update(time);
			
			scrollingPlane.scrollRight(
				mouseScrollFilter.getOutputX() + keyboardScrollFilter.getOutputX());
			scrollingPlane.scrollUp(
				mouseScrollFilter.getOutputY() + keyboardScrollFilter.getOutputY());
		} else {
			scrollingPlane.setLocationFromWolrdTranslation(
					followObject.getWorldTranslation());
		}
		
		// rotation
		keyboardHFilter.update(time);
		keyboardVFilter.update(time);
		mouseLookFilter.update(time);
		
		if (verticalRotationEnabled) {
			rotationSphere.rotateVertical(
				mouseLookFilter.getOutputY() + keyboardVFilter.getOutput());
		}
		
		float rotH = 0;
		if (horizontalRotationEnabled) {
			rotH = mouseLookFilter.getOutputX() + keyboardHFilter.getOutput();
		}
		
		if (followingObjectRotation) {
			followAngleOffset += rotH;
			
			float followAngle = rotationSphere.computeHorizontalAngle(
					followObject.getWorldRotation());
			followRotation.setGoal(followAngle);
			followRotation.update(time);
			
			rotationSphere.getHorizontal().setCurrent(
					followRotation.getCurrent() + followAngleOffset);
			
		} else {
			rotationSphere.rotateHorizontal(rotH);
		}
		
		// zoom
		keyboardZoomFilter.update(time);
		mouseZoomFilter.update(time);
		
		rotationSphere.zoomOut(mouseZoomFilter.getOutput()
				+ keyboardZoomFilter.getOutput());
		
		// apply
		updateCameraLocation();
		
		if (cameraEffects != null) {
			cameraEffects.update(time);
		} else {
			camera.setLocation(finalCameraLocation);
			camera.lookAt(finalWorldTranslation, rotationSphere.getWorldUp());
		}
	}
	
	protected void handleMouse(float time) {
		evt.setTime(time);
		
		mouseScrolling = false;
		mouseRotating = false;

		if(keyboard.isValidCommand(CMD_KEY_TOGGLE_HARDWARE_MOUSE, false)) {
			mouseManager.setNativeMousePreferred(
					!mouseManager.isNativeMousePreferred());
		}
		
		if (keyboard.isValidCommand(CMD_KEY_TOGGLE_MOUSE_LOOK) ||
				mouseBinding.isValidCommand(CMD_MB_TOGGLE_MOUSE_LOOK))
		{
			if (mouseLookEnabled) {
				mouseManager.useSuspendedCursor(CURSOR_ROTATE);
				mouseLook.performAction(evt);
				
				mouseRotating = true;
			}
			
		} else if (followObject == null) {
			if (mouseScrollEnabled) {
				mouseScrolling = mouseScroll.performAction(evt);
			}
		}
		
		mouseZoom.performAction(evt);
	}
	
	protected void handleKeyboard(float time) {
		if (keyboardLookEnabled) {
			if (keyboard.isValidCommand(CMD_KEY_ROTATE_UP)) {
				keyboardVFilter.feedInput(-time);
			}
			if (keyboard.isValidCommand(CMD_KEY_ROTATE_DOWN)) {
				keyboardVFilter.feedInput(time);
			}
			if (keyboard.isValidCommand(CMD_KEY_ROTATE_LEFT)) {
				keyboardHFilter.feedInput(time);
			}
			if (keyboard.isValidCommand(CMD_KEY_ROTATE_RIGHT)) {
				keyboardHFilter.feedInput(-time);
			}
			if (keyboard.isValidCommand(CMD_KEY_ZOOM_IN)) {
				keyboardZoomFilter.feedInput(-time);
			}
			if (keyboard.isValidCommand(CMD_KEY_ZOOM_OUT)) {
				keyboardZoomFilter.feedInput(time);
			}
		}
		
		if (keyboardScrollEnabled && followObject == null) {
			if (keyboard.isValidCommand(CMD_KEY_SCROLL_UP)) {
				keyboardScrollFilter.feedInputY(time);
			}
			if (keyboard.isValidCommand(CMD_KEY_SCROLL_DOWN)) {
				keyboardScrollFilter.feedInputY(-time);
			}
			if (keyboard.isValidCommand(CMD_KEY_SCROLL_LEFT)) {
				keyboardScrollFilter.feedInputX(-time);
			}
			if (keyboard.isValidCommand(CMD_KEY_SCROLL_RIGHT)) {
				keyboardScrollFilter.feedInputX(time);
			}
		}
	}
	
	public HeightMap getHeightMap() {
		return heightMap;
	}
	
	public CameraEffects getCameraEffects() {
		return cameraEffects;
	}
	
	public void setCameraEffects(CameraEffects cameraEffects) {
		this.cameraEffects = cameraEffects;
	}
	
	public Camera getCamera() {
		return camera;
	}
	
	public void setCamera(Camera camera) {
		this.camera = camera;
		scrollingPlane.setCamera(camera);
		if (cameraEffects != null) cameraEffects.setCamera(camera);
	}
	
	/**
	 * This method check if the handler is currenly using mouse input
	 * for internal purposes. You should not use the mouse when the
	 * handler is using it. So when this method returns true, you should
	 * not do mouse picking. 
	 * 
	 * Also you can change the cursors directly via MouseManager without
	 * overring the special cursors (while scrolling and rotating) provided
	 * that you check with this method first.
	 * 
	 * @return true if the handler is using mouse input internally.
	 */
	public boolean isUsingMouseInput() {
		return (mouseRotating || mouseScrolling);
	}
	
	public Vector2f getMousePosition() {
		return mouseManager.getMousePosition();
	}
	
	public Vector3f getWorldTranslation() {
		return finalWorldTranslation;
	}
	
	public Vector3f getCameraLocation() {
		return finalCameraLocation;
	}
	
	/**
	 * Will clear any remaining filter effects.
	 * 
	 * @param worldTranslation
	 */
	public void setWorldTranslation(Vector3f worldTranslation) {
		setWorldTranslation(
				worldTranslation.x, worldTranslation.y, worldTranslation.z);
	}
	
	/**
	 * Will clear any remaining filter effects.
	 * 
	 * @param x
	 * @param y
	 * @param z
	 */
	public void setWorldTranslation(float x, float y, float z) {
		clearLookFilterEffects();
		clearScrollFilterEffects();
		
		scrollingPlane.setLocationFromWolrdTranslation(x, y, z);
		updateCameraLocation();
	}
	
	public void updateCameraLocation() {
		cameraLocationNoHeight.set(scrollingPlane.getWorldTranslation());
		cameraLocationNoHeight.addLocal(rotationSphere.findOnSphereLocation());
		
		updateHeight();
	}
	
	public void clearFilterEffects() {
		clearLookFilterEffects();
		clearScrollFilterEffects();
	}
	
	/**
	 * Will clear remaining rotation and zoom filter effects.
	 * Useful when manually setting rotation values on rotationSphere.
	 */
	public void clearLookFilterEffects() {
		mouseZoomFilter.clear();
		mouseLookFilter.clear();
		
		keyboardZoomFilter.clear();
		keyboardHFilter.clear();
		keyboardVFilter.clear();
	}
	
	/**
	 * Will clear remaining scroll filter effects.
	 * Useful when manually setting values on scrollingPlane.
	 */
	public void clearScrollFilterEffects() {
		mouseScrollFilter.clear();
		keyboardScrollFilter.clear();
	}
	
	/**
	 * Object position will be followed, object rotation will be ignored.
	 *  
	 * @param object
	 */
	public void setFollowObject(Spatial object) {
		setFollowObject(object, false, false, 0);
	}
	
	/**
	 * Both object position and rotation will be followed.
	 * 
	 * @param object
	 * @param alignView true to position the view behind the object, false otherwise
	 */
	public void setFollowObject(Spatial object, boolean alignView) {
		setFollowObject(object, true, alignView, 0);
	}
	
	/**
	 * Both object position and rotation will be followed. The view is positioned
	 * behind the object then repositioned on the sphere according to alignOffset.
	 * 
	 * @param object
	 * @param alignOffset in degrees
	 */
	public void setFollowObject(Spatial object, float alignOffset) {
		setFollowObject(object, true, true, alignOffset);
	}
	
	/**
	 * Setting an object to follow will cause the handler to keep the camera
	 * centered on that object.
	 * This will disable the scrolling functionality. If you want to restore
	 * the scrolling functionality, set a null object to follow.
	 * 
	 * @param object an object to follow
	 * @param followRotation
	 * @param alignView only meaningful if followRotation is true
	 * @param alignOffset only meaningful if alightView is true
	 */
	public void setFollowObject(Spatial object,
			boolean followRotation, boolean alignView, float alignOffset)
	{
		boolean objectChanged = (followObject != object);
		followObject = object;
		
		if (followObject == null) {
			followingObjectRotation = false;
			return;
		}
		
		if (objectChanged) {
			setWorldTranslation(followObject.getWorldTranslation());
		}
		
		float objectAngle = 0;
		if (objectChanged || (followRotation && !followingObjectRotation)) {
			objectAngle = rotationSphere.computeHorizontalAngle(
					followObject.getWorldRotation());
			this.followRotation.init(objectAngle, objectAngle);
		}
		
		if (alignView) {
			followAngleOffset = alignOffset;
			
		} else if (objectChanged
				|| (followRotation && !followingObjectRotation))
		{
			followAngleOffset =
				rotationSphere.getHorizontal().getCurrent() - objectAngle;
		}
		
		followingObjectRotation = followRotation;
	}
	
	public Spatial getFollowObject() {
		return followObject;
	}
	
	public boolean isFollowingObjectRotation() {
		return followingObjectRotation;
	}
	
	/**
	 * Extend this class and override this method if you want to use
	 * a different height offset algorithm.
	 */
	protected void updateHeight() {
		if (heightMap == null) {
			finalWorldTranslation.set(scrollingPlane.getWorldTranslation());
			finalCameraLocation.set(cameraLocationNoHeight);
			return;
		}
		
		float pointHeight = heightMap.getHeightAt(
				scrollingPlane.getWorldTranslation());
		float camHeight = heightMap.getHeightAt(cameraLocationNoHeight);
		camHeight += camHeightOffset;
		
		Vector3f observedPoint = scrollingPlane.getWorldTranslation();
		Vector3f camLocation = cameraLocationNoHeight;
		
		float camPointOffset = scrollingPlane.getUpVector().dot(camLocation)
									- scrollingPlane.getHeight();
		
		float camFromPoint = pointHeight + camPointOffset;
		
		float finalPointHeight;
		float finalCamHeight;
		if (camFromPoint > camHeight) {
			finalPointHeight = pointHeight;
			finalCamHeight = camFromPoint;
		} else {
			finalPointHeight = camHeight - camPointOffset;
			finalCamHeight = camHeight;
		}
		
		finalWorldTranslation.set(scrollingPlane.getUpVector());
		finalWorldTranslation.multLocal(finalPointHeight);
		finalWorldTranslation.addLocal(observedPoint);
		
		finalCameraLocation.set(scrollingPlane.getUpVector());
		finalCameraLocation.multLocal(finalCamHeight - camPointOffset);
		finalCameraLocation.addLocal(camLocation);
	}
	
	public MouseManager getMouseManager() {
		return mouseManager;
	}
	
	/**
	 * You should use this method to set the cursor every frame to
	 * ensure the correct cursor updates. If the cursor you are setting
	 * is the same as before, no actual changes are made.
	 * 
	 * Note: you should not change cursors via the MouseManager directly,
	 * unless you have checked that StrategicHandler is not using
	 * mouse input at the moment.
	 * 
	 * @param name the name of the cursor to use
	 */
	public void useCursor(String name) {
		if (!isUsingMouseInput()) {
			mouseManager.useCursor(name);
		}
	}
	
	public boolean isVerticalRotationEnabled() {
		return this.verticalRotationEnabled;
	}
	
	public void setVerticalRotationEnabled(boolean enabled) {
		this.verticalRotationEnabled = enabled;
	}
	
	public boolean isHorizontalRotationEnabled() {
		return this.horizontalRotationEnabled;
	}
	
	public void setHorizontalRotationEnabled(boolean enabled) {
		this.horizontalRotationEnabled = enabled;
	}
	
	public boolean isZoomEnabled() {
		return zoomEnabled;
	}
	
	public void setZoomEnabled(boolean enabled) {
		zoomEnabled = enabled;
	}
	
	
	// *** Config **************************************************************
	
	@SuppressWarnings("unchecked")
	public void updateProperties(HashMap props) {
		camHeightOffset =
			getFloatProp(props,	PROP_CAM_HEIGHT_OFFSET,	camHeightOffset);

		setKeyboardLookEnabled(getBooleanProp(props,
				PROP_KEYBOARD_LOOK_ENABLED, isKeyboardLookEnabled()));
		setKeyboardScrollEnabled(getBooleanProp(props,
				PROP_KEYBOARD_SCROLL_ENABLED,isKeyboardScrollEnabled()));
		setMouseLookEnabled(getBooleanProp(props,
				PROP_MOUSE_LOOK_ENABLED, isMouseLookEnabled()));
		setMouseScrollEnabled(getBooleanProp(props,
				PROP_MOUSE_SCROLL_ENABLED, isMouseScrollEnabled()));
		
		mouseLook.setXInverted(getBooleanProp(props,
				PROP_MOUSE_INVERT_X, false));
		mouseLook.setYInverted(getBooleanProp(props,
				PROP_MOUSE_INVERT_Y, false));
		mouseZoom.setZInverted(getBooleanProp(props,
				PROP_MOUSE_INVERT_WEEL, false));
		mouseScroll.setBorder(getFloatProp(props,
				PROP_MOUSE_SCROLL_BORDER, mouseScroll.getBorder()));
		
		followRotation.setSpringConstant(getFloatProp(props,
				PROP_FOLLOW_SPRING,
				followRotation.getSpringConstant()));
		followRotation.setDamping(getFloatProp(props,
				PROP_FOLLOW_DAMPING,
				followRotation.getDamping()));
		
		mouseLook.setSensitivity(getFloatProp(props,
				PROP_MOUSE_ROTATION_SENSITIVITY,mouseLook.getSensitivity()));
		mouseZoom.setSensitivity(getFloatProp(props,
				PROP_MOUSE_ZOOM_SENSITIVITY,mouseZoom.getSensitivity()));
		
		mouseLookFilter.setInterpolationTime(getFloatProp(props,
				PROP_MOUSE_ROTATION_INTERPOLATION,
				mouseLookFilter.getInterpolationTime()));
		
		mouseZoomFilter.setSpringConstant(getFloatProp(props,
				PROP_MOUSE_ZOOM_SPRING, mouseZoomFilter.getSpringConstant()));
		mouseZoomFilter.setDamping(getFloatProp(props,
				PROP_MOUSE_ZOOM_DAMPING, mouseZoomFilter.getDamping()));
		
		keyboardHFilter.setForce(getFloatProp(props,
				PROP_KEYBOARD_HORIZONTAL_FORCE, keyboardHFilter.getForce()));
		keyboardHFilter.setDamping(getFloatProp(props,
				PROP_KEYBOARD_HORIZONTAL_DAMPING, keyboardHFilter.getDamping()));
		keyboardHFilter.setMaxSpeed(getFloatProp(props,
				PROP_KEYBOARD_HORIZONTAL_MAXSPEED,
				keyboardHFilter.getMaxSpeed()));
		
		keyboardVFilter.setForce(getFloatProp(props,
				PROP_KEYBOARD_VERTICAL_FORCE, keyboardVFilter.getForce()));
		keyboardVFilter.setDamping(getFloatProp(props,
				PROP_KEYBOARD_VERTICAL_DAMPING, keyboardVFilter.getDamping()));
		keyboardVFilter.setMaxSpeed(getFloatProp(props,
				PROP_KEYBOARD_VERTICAL_MAXSPEED,
				keyboardVFilter.getMaxSpeed()));
		
		keyboardZoomFilter.setForce(getFloatProp(props,
				PROP_KEYBOARD_ZOOM_FORCE, keyboardZoomFilter.getForce()));
		keyboardZoomFilter.setDamping(getFloatProp(props,
				PROP_KEYBOARD_ZOOM_DAMPING, keyboardZoomFilter.getDamping()));
		keyboardZoomFilter.setMaxSpeed(getFloatProp(props,
				PROP_KEYBOARD_ZOOM_MAXSPEED, keyboardZoomFilter.getMaxSpeed()));
		
		mouseScrollFilter.setForce(getFloatProp(props,
				PROP_MOUSE_SCROLL_FORCE, mouseScrollFilter.getForce()));
		mouseScrollFilter.setDamping(getFloatProp(props,
				PROP_MOUSE_SCROLL_DAMPING, mouseScrollFilter.getDamping()));
		mouseScrollFilter.setMaxSpeed(getFloatProp(props,
				PROP_MOUSE_SCROLL_MAXSPEED, mouseScrollFilter.getMaxSpeed()));
		
		keyboardScrollFilter.setForce(getFloatProp(props,
				PROP_KEYBOARD_SCROLL_FORCE, keyboardScrollFilter.getForce()));
		keyboardScrollFilter.setDamping(getFloatProp(props,
				PROP_KEYBOARD_SCROLL_DAMPING, keyboardScrollFilter.getDamping()));
		keyboardScrollFilter.setMaxSpeed(getFloatProp(props,
				PROP_KEYBOARD_SCROLL_MAXSPEED,
				keyboardScrollFilter.getMaxSpeed()));
		
		MinMaxCurrent vAngle = rotationSphere.getVertical();
		vAngle.setMin(getFloatProp(props,
				PROP_VERTICAL_ROTATION_MIN, vAngle.getMin()));
		vAngle.setMax(getFloatProp(props,
				PROP_VERTICAL_ROTATION_MAX, vAngle.getMax()));
		vAngle.setCurrent(getFloatProp(props,
				PROP_VERTICAL_ROTATION_CURRENT, vAngle.getCurrent()));
		
		MinMaxCurrent hAngle = rotationSphere.getHorizontal();
		hAngle.setMin(getFloatProp(props,
				PROP_HORIZONTAL_ROTATION_MIN, hAngle.getMin()));
		hAngle.setMax(getFloatProp(props,
				PROP_HORIZONTAL_ROTATION_MAX, hAngle.getMax()));
		hAngle.setCurrent(getFloatProp(props,
				PROP_HORIZONTAL_ROTATION_CURRENT, hAngle.getCurrent()));
		if (hAngle.getMax() - hAngle.getMin() == 360) {
			hAngle.setLoop(true);
		} else {
			hAngle.setLoop(false);
		}
		
		MinMaxCurrent zoom = rotationSphere.getZoom();
		zoom.setMin(getFloatProp(props,
				PROP_ZOOM_DISTANCE_MIN, zoom.getMin()));
		zoom.setMax(getFloatProp(props,
				PROP_ZOOM_DISTANCE_MAX, zoom.getMax()));
		zoom.setCurrent(getFloatProp(props,
				PROP_ZOOM_DISTANCE_CURRENT, zoom.getCurrent()));
		

		Vector3f worldUp = (Vector3f) getObjectProp(props, PROP_WORLD_UP, null);
		
		Vector3f vec1 = (Vector3f) getObjectProp(props, PROP_SCROLL_XVEC, null);
		Vector3f vec2 = (Vector3f) getObjectProp(props, PROP_SCROLL_YVEC, null);
		if (vec1 != null && vec2 != null && worldUp != null) {
			scrollingPlane.setPlane(vec1, vec2, worldUp);
		}
		
		Vector3f dir = (Vector3f) getObjectProp(props, PROP_DIRECTION, null);
		Vector3f worldLeft=(Vector3f) getObjectProp(props,PROP_WORLD_LEFT,null);
		if (dir != null && worldUp != null && worldLeft != null) {
			rotationSphere.setVectors(dir, worldUp, worldLeft);
		}
		
	}
	
	/**
	 * If you specify exactly one diagonal scroll cursor, the other diagonal
	 * scroll cursors will be created automatically by rotating the
	 * specified cursor.
	 * Same goes for nondiagonal scroll cursor.
	 * 
	 * Diagonal means up-left, down-left, down-right or up-right.
	 * Nondiagonal means up, left, down or right.
	 */
	public void updateCursors(Map<String, String> props) {
		mouseManager.setCursor(CURSOR_ROTATE,
				retrieveCursor(props.get(CURSOR_ROTATE)));
		
		assignScrollCursors(new String[]{
				CURSOR_SCROLL_UP,
				CURSOR_SCROLL_LEFT,
				CURSOR_SCROLL_DOWN,
				CURSOR_SCROLL_RIGHT
		}, props);
		
		assignScrollCursors(new String[]{
				CURSOR_SCROLL_UPLEFT,
				CURSOR_SCROLL_DOWNLEFT,
				CURSOR_SCROLL_DOWNRIGHT,
				CURSOR_SCROLL_UPRIGHT
		}, props);
	}
	
	private void assignScrollCursors(String[] keys, Map<String, String> props) {
		Cursor[] scrollCursors = new Cursor[keys.length];
		int nonNullCursorCount = 0;
		int lastNonNullCursor = 0;
		
		for (int i = 0; i < keys.length; i++) {
			Cursor cursor = retrieveCursor(props.get(keys[i]));
			scrollCursors[i] = cursor;
			if (cursor != null) {
				lastNonNullCursor = i;
				nonNullCursorCount++;
			}
		}
		
		if (nonNullCursorCount == 1) {
			
			Cursor nonNullCursor = scrollCursors[lastNonNullCursor];
			
			int rotation = 0;
			int currentIndex = lastNonNullCursor;
			
			for (int i = 0; i < scrollCursors.length - 1; i++) {
				rotation += 90;
				currentIndex++;
				
				if (currentIndex >= scrollCursors.length) currentIndex = 0;
				
				scrollCursors[currentIndex] = nonNullCursor.getRotatedCursor(
						keys[currentIndex], rotation);
			}
		}
		
		for (int i = 0; i < keys.length; i++) {
			mouseManager.setCursor(keys[i], scrollCursors[i]);
		}
	}
	
	private Cursor retrieveCursor(Object object) {
		Cursor cursor = null;
		
		if (object instanceof String) {
			cursor = Cursor.load(
				this.getClass().getClassLoader().getResource((String) object));
			
		} else if (object instanceof Cursor) {
			cursor = (Cursor) object;
		}
		
		return cursor;
	}
	
	@SuppressWarnings("unchecked")
	public void updateBindings(HashMap props) {
		mouseBinding.setCommand(CMD_MB_TOGGLE_MOUSE_LOOK, getIntProp(props,
			CMD_MB_TOGGLE_MOUSE_LOOK, DEFAULT_MB_TOGGLE_MOUSE_LOOK));
		
		keyboard.set(CMD_KEY_TOGGLE_MOUSE_LOOK, getIntProp(props,
			CMD_KEY_TOGGLE_MOUSE_LOOK, DEFAULT_KEY_TOGGLE_MOUSE_LOOK));
		keyboard.set(CMD_KEY_TOGGLE_HARDWARE_MOUSE, getIntProp(props,
			CMD_KEY_TOGGLE_HARDWARE_MOUSE, DEFAULT_KEY_TOGGLE_HARDWARE_MOUSE));
		
		keyboard.set(CMD_KEY_SCROLL_UP,	getIntProp(props,
				CMD_KEY_SCROLL_UP, DEFAULT_KEY_SCROLL_UP));
		keyboard.set(CMD_KEY_SCROLL_DOWN, getIntProp(props,
				CMD_KEY_SCROLL_DOWN, DEFAULT_KEY_SCROLL_DOWN));
		keyboard.set(CMD_KEY_SCROLL_RIGHT, getIntProp(props,
				CMD_KEY_SCROLL_RIGHT, DEFAULT_KEY_SCROLL_RIGHT));
		keyboard.set(CMD_KEY_SCROLL_LEFT, getIntProp(props,
				CMD_KEY_SCROLL_LEFT, DEFAULT_KEY_SCROLL_LEFT));
		
		keyboard.set(CMD_KEY_ROTATE_UP, getIntProp(props,
				CMD_KEY_ROTATE_UP, DEFAULT_KEY_ROTATE_UP));
		keyboard.set(CMD_KEY_ROTATE_DOWN, getIntProp(props,
				CMD_KEY_ROTATE_DOWN, DEFAULT_KEY_ROTATE_DOWN));
		keyboard.set(CMD_KEY_ROTATE_LEFT, getIntProp(props,
				CMD_KEY_ROTATE_LEFT, DEFAULT_KEY_ROTATE_LEFT));
		keyboard.set(CMD_KEY_ROTATE_RIGHT, getIntProp(props,
				CMD_KEY_ROTATE_RIGHT, DEFAULT_KEY_ROTATE_RIGHT));
		keyboard.set(CMD_KEY_ZOOM_IN, getIntProp(props,
				CMD_KEY_ZOOM_IN, DEFAULT_KEY_ZOOM_IN));
		keyboard.set(CMD_KEY_ZOOM_OUT, getIntProp(props,
				CMD_KEY_ZOOM_OUT, DEFAULT_KEY_ZOOM_OUT));
	}
	
	
	// *** Config related getters and setters **********************************
	
	/**
	 * @return the rotationSphere
	 */
	public RotationSphere getRotationSphere() {
		return rotationSphere;
	}

	/**
	 * @return the scrollingPlane
	 */
	public ScrollingPlane getScrollingPlane() {
		return scrollingPlane;
	}
	
	public float getCamHeightOffset() {
		return camHeightOffset;
	}

	public void setCamHeightOffset(float camHeightOffset) {
		this.camHeightOffset = camHeightOffset;
	}

	public boolean isKeyboardLookEnabled() {
		return keyboardLookEnabled;
	}
	
	public void setKeyboardLookEnabled(boolean enabled) {
		keyboardLookEnabled = enabled;
	}
	
	public boolean isKeyboardScrollEnabled() {
		return keyboardScrollEnabled;
	}
	
	public void setKeyboardScrollEnabled(boolean enabled) {
		keyboardScrollEnabled = enabled;
	}
	
	public boolean isMouseLookEnabled() {
		return mouseLookEnabled;
	}
	
	public void setMouseLookEnabled(boolean enabled) {
		mouseLookEnabled = enabled;
	}
	
	public boolean isMouseScrollEnabled() {
		return mouseScrollEnabled;
	}
	
	public void setMouseScrollEnabled(boolean enabled) {
		mouseScrollEnabled = enabled;
	}
	
	public AccelFilter getKeyboardHFilter() {
		return keyboardHFilter;
	}

	public AccelFilter2d getKeyboardScrollFilter() {
		return keyboardScrollFilter;
	}

	public AccelFilter getKeyboardVFilter() {
		return keyboardVFilter;
	}

	public AccelFilter getKeyboardZoomFilter() {
		return keyboardZoomFilter;
	}

	public LinearFilter2d getMouseLookFilter() {
		return mouseLookFilter;
	}

	public AccelFilter2d getMouseScrollFilter() {
		return mouseScrollFilter;
	}

	public SpringFilter getMouseZoomFilter() {
		return mouseZoomFilter;
	}
	
	
	// *** Property keys *******************************************************
	
	public static final String PROP_CAM_HEIGHT_OFFSET = "camHeightOffset";

	public static final String PROP_KEYBOARD_LOOK_ENABLED = "kbLookEnabled";
	public static final String PROP_KEYBOARD_SCROLL_ENABLED = "kbScrollEnabled";
	public static final String PROP_MOUSE_LOOK_ENABLED = "mLookEnabled";
	public static final String PROP_MOUSE_SCROLL_ENABLED = "mScrollEnabled";
	
	public static final String PROP_MOUSE_INVERT_X = "invertMouseX";
	public static final String PROP_MOUSE_INVERT_Y = "invertMouseY";
	public static final String PROP_MOUSE_INVERT_WEEL = "invertMouseZ";
	public static final String PROP_MOUSE_SCROLL_BORDER = "scrollBorder";
	
	public static final String PROP_FOLLOW_SPRING = "followSpring";
	public static final String PROP_FOLLOW_DAMPING = "followDamping";
	
	public static final String PROP_MOUSE_ROTATION_SENSITIVITY = "mRotSens";
	public static final String PROP_MOUSE_ROTATION_INTERPOLATION = "mRotInterp";
	public static final String PROP_MOUSE_ZOOM_SENSITIVITY = "mZoomSens";
	public static final String PROP_MOUSE_ZOOM_SPRING = "mZoomSpring";
	public static final String PROP_MOUSE_ZOOM_DAMPING = "mZoomDamping";
	
	public static final String PROP_KEYBOARD_HORIZONTAL_FORCE = "kbHRotForce";
	public static final String PROP_KEYBOARD_HORIZONTAL_DAMPING = "kbHRotDamp";
	public static final String PROP_KEYBOARD_HORIZONTAL_MAXSPEED = "kbHRotMs";
	public static final String PROP_KEYBOARD_VERTICAL_FORCE = "kbVRotForce";
	public static final String PROP_KEYBOARD_VERTICAL_DAMPING = "kbVRotDamp";
	public static final String PROP_KEYBOARD_VERTICAL_MAXSPEED = "kbVRotMs";
	public static final String PROP_KEYBOARD_ZOOM_FORCE = "kbZoomForce";
	public static final String PROP_KEYBOARD_ZOOM_DAMPING = "kbZoomDamp";
	public static final String PROP_KEYBOARD_ZOOM_MAXSPEED = "kbZoomMs";
	
	public static final String PROP_MOUSE_SCROLL_FORCE = "mScrollForce";
	public static final String PROP_MOUSE_SCROLL_DAMPING = "mScrollDamping";
	public static final String PROP_MOUSE_SCROLL_MAXSPEED = "mScrollMs";
	public static final String PROP_KEYBOARD_SCROLL_FORCE = "kbScrollForce";
	public static final String PROP_KEYBOARD_SCROLL_DAMPING = "kbScrollDamping";
	public static final String PROP_KEYBOARD_SCROLL_MAXSPEED = "kbScrollMs";
	
	public static final String PROP_VERTICAL_ROTATION_MIN = "vRotMin";
	public static final String PROP_VERTICAL_ROTATION_MAX = "vRotMax";
	public static final String PROP_VERTICAL_ROTATION_CURRENT = "vRotCurrent";
	public static final String PROP_HORIZONTAL_ROTATION_MIN = "hRotMin";
	public static final String PROP_HORIZONTAL_ROTATION_MAX = "hRotMax";
	public static final String PROP_HORIZONTAL_ROTATION_CURRENT = "hRotCurrent";
	public static final String PROP_ZOOM_DISTANCE_MIN = "zoomDistMin";
	public static final String PROP_ZOOM_DISTANCE_MAX = "zoomDistMax";
	public static final String PROP_ZOOM_DISTANCE_CURRENT = "zoomDistCurrent";
	
	public static final String PROP_SCROLL_XVEC = "scrollVec1";
	public static final String PROP_SCROLL_YVEC = "scrollVec2";
	public static final String PROP_DIRECTION = "direction";
	public static final String PROP_WORLD_UP = "worldUp";
	public static final String PROP_WORLD_LEFT = "worldLeft";
	
	public static final String CURSOR_ROTATE = "rotateCursor";
	public static final String CURSOR_SCROLL_UP = "scrollUCursor";
	public static final String CURSOR_SCROLL_UPLEFT = "scrollULCursor";
	public static final String CURSOR_SCROLL_LEFT = "scrollLCursor";
	public static final String CURSOR_SCROLL_DOWNLEFT = "scrollDLCursor";
	public static final String CURSOR_SCROLL_DOWN = "scrollDCursor";
	public static final String CURSOR_SCROLL_DOWNRIGHT = "scrollDRCursor";
	public static final String CURSOR_SCROLL_RIGHT = "scrollRCursor";
	public static final String CURSOR_SCROLL_UPRIGHT = "scrollURCursor";
	
	public static final String CMD_MB_TOGGLE_MOUSE_LOOK = "mouse_toggleMl";
	public static final String CMD_KEY_TOGGLE_MOUSE_LOOK = "key_toggleMl";
	public static final String CMD_KEY_TOGGLE_HARDWARE_MOUSE = "key_toggleHwm";
	
	public static final String CMD_KEY_ROTATE_UP = "key_rotateUp";
	public static final String CMD_KEY_ROTATE_DOWN = "key_rotateDown";
	public static final String CMD_KEY_ROTATE_LEFT = "key_rotateLeft";
	public static final String CMD_KEY_ROTATE_RIGHT = "key_rotateRight";
	public static final String CMD_KEY_ZOOM_IN = "key_zoomIn";
	public static final String CMD_KEY_ZOOM_OUT = "key_zoomOut";
	
	public static final String CMD_KEY_SCROLL_UP = "key_scrollUp";
	public static final String CMD_KEY_SCROLL_DOWN = "key_scrollDown";
	public static final String CMD_KEY_SCROLL_LEFT = "key_scrollLeft";
	public static final String CMD_KEY_SCROLL_RIGHT = "key_scrollRight";
	
}
